/*
 * Copyright 2010 - 2011, The PLDesktop Development Team
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

#include "pic.h"

#include <kerneldebugconsole.h>

#define BASE_INTERRUPT_VECTOR 0x20

PLPic::PLPic() : m_SlavePort("PIC #2"), m_MasterPort("PIC #1") {
	for(uint8_t i = 0 ; i < 16 ; i++) {
		m_HandlerEdge[i] = false;
	}
}

bool PLPic::start() {
	if(m_SlavePort.allocate(0xA0, 4) == false) {
		return false;
	}
	if(m_MasterPort.allocate(0x20, 4) == false) {
		return false;
	}
	// Initialise the slave and master PIC
	m_MasterPort.write8(0x11, 0);
	m_SlavePort.write8(0x11, 0);
	m_MasterPort.write8(BASE_INTERRUPT_VECTOR, 1);
	m_SlavePort.write8(BASE_INTERRUPT_VECTOR + 0x08, 1);
	m_MasterPort.write8(0x04, 1);
	m_SlavePort.write8(0x02, 1);
	m_MasterPort.write8(0x01, 1);
	m_SlavePort.write8(0x01, 1);
	// Register the interrupts
	PLInterruptManager &intManager = PLProcessor::instance().interruptManager();
	for(uint8_t i = 0 ; i < 16 ; i++) {
		if(intManager.registerInterruptHandler(i + BASE_INTERRUPT_VECTOR, this) == false) {
			return false;
		}
	}
	// Disable all IRQ's (exept IRQ2)
	enableAll(false);
	return true;
}

uint32_t PLPic::registerIsaIrqHandler(uint8_t irq, PLIrqHandler *handler, bool bEdge) {
	if(irq >= 16) {
		return 0;
	}
	m_Handler[irq].append(handler);
	m_HandlerEdge[irq] = bEdge;
	enable(irq, true);
	return irq + BASE_INTERRUPT_VECTOR;
}

void PLPic::acknowledgeIrq(uint32_t Id) {
	uint8_t irq = Id - BASE_INTERRUPT_VECTOR;
	enable(irq, true);
	eoi(irq);
}

void PLPic::unregisterHandler(uint32_t Id, PLIrqHandler *handler) {
	uint8_t irq = Id - BASE_INTERRUPT_VECTOR;
	if(!m_Handler[irq].count()) {
		enable(irq, false);
	}
	for(uint32_t i = 0 ; i < m_Handler[irq].count() ; i++) {
		if(m_Handler[irq].at(i) == handler) {
			m_Handler[irq].remove(i);
			return;
		}
	}
}

bool PLPic::interrupt(size_t nInterruptNumber, InterruptState &state) {
	uint8_t irq = (nInterruptNumber - BASE_INTERRUPT_VECTOR);
	if(m_Handler[irq].count() != 0) {
		if(m_HandlerEdge[irq]) {
			eoi(irq);
		}
		bool bHandled = false;
		for(uint32_t i = 0 ; i < m_Handler[irq].count() ; i++) {
			bool tmp = m_Handler[irq].at(i)->irq(irq, state);
			if((!bHandled) && tmp) {
				bHandled = true;
			}
		}
		if(!bHandled) {
			enable(irq, false);
		}
		if(!m_HandlerEdge[irq]) {
			eoi(irq);
		}
	}
	return true;
}

void PLPic::enable(uint8_t irq, bool _enable) {
	if(irq <= 7) {
		uint8_t mask = m_MasterPort.read8(1);
		if(_enable) {
			mask = mask & ~(1 << irq);
		} else {
			mask = mask | (1 << irq);
		}
		m_MasterPort.write8(mask, 1);
	} else {
		uint8_t mask = m_SlavePort.read8(1);
		if(_enable) {
			mask = mask & ~(1 << (irq - 8));
		} else {
			mask = mask | (1 << (irq - 8));
		}
		m_SlavePort.write8(mask, 1);
	}
}

void PLPic::eoi(uint8_t irq) {
	m_MasterPort.write8(0x20, 0);
	if(irq > 7) {
		m_SlavePort.write8(0x20, 0);
	}
}

void PLPic::enableAll(bool _enable) {
	if(!_enable) {
		m_MasterPort.write8(0xFB, 1);
		m_SlavePort.write8(0xFB, 1);
	} else {
		m_MasterPort.write8(0x00, 1);
		m_SlavePort.write8(0x00, 1);
	}
}
